package com.embedsky.administrator.mycardemulation.socket;

import android.os.SystemClock;
import android.util.Log;

import com.embedsky.administrator.mycardemulation.executor.AppExecutor;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

public abstract class SocketHandler extends Thread {
    private static final String TAG = "SocketHandler";
    private OutputStream mOutput;
    private InputStream mInput;
    private final Socket mSocket;
    private volatile boolean mRunning;
    private final String mIp;
    private ConcurrentLinkedQueue<byte[]> mQueue = new ConcurrentLinkedQueue<>();
    private long mHeardTime = -1;
    public static final int HEARD_INTERVAL_TIME = 400;
    //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 start
    //time 3000 -> 2100 reduce heat check time to avoid reconnect when in heat checking;
    public static final int HEARD_BREAK_TIME = 2100;
    //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 end
    private Runnable mHeardBeatRunnable;

    /**
     * Instantiates a new Socket handler.
     *
     * @param socket the socket
     * @param ip     the ip
     */
    public SocketHandler(Socket socket, String ip) {
        super("socket-" + ip);
        mSocket = socket;
        mIp = ip;
        try {
            mInput = socket.getInputStream();
            mOutput = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mRunning = true;
        mHeardBeatRunnable = () -> {
            sendHeardBeat();
        };
        AppExecutor.getInstance().scheduledExecutor().postInterval(mHeardBeatRunnable, HEARD_INTERVAL_TIME, HEARD_INTERVAL_TIME);
        start();
    }

    @Override
    public void run() {
        // make thread priority default
        //Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (mRunning) {
            if (!mSocket.isConnected() || mSocket.isClosed()) {
                Log.d(TAG, "SocketHandler has closed!");
                disconnect(mIp);
                finish();
                break;
            }
            try {
                if (-1 == mHeardTime) {
                    mHeardTime = SystemClock.elapsedRealtime();
                }
                checkHeardBreaked();
                int available = mInput.available();
                if (available > 0) {
                    byte[] bytes = new byte[available];
                    mInput.read(bytes);
                    Log.d(TAG, "tag run get: " + bytes.length);
                    List<byte[]> list = ByteUtil.parseBytes(bytes, () -> {
                        heardBeat();
                    });
                    for (int i = 0; i < list.size(); i++) {
                        parseData(list.get(i), mIp);
                    }
                }
                synchronized (this) {
                    if (mRunning && !mQueue.isEmpty()) {
                        try {
                            byte[] bytes = mQueue.poll();
                            if (bytes == null) {
                                continue;
                            }
                            Log.d(TAG, "send run: " + new String(bytes));
                            byte[] packBytes = ByteUtil.packBytes(bytes);
                            mOutput.write(packBytes);
                            mOutput.flush();
                        } catch (Exception ex) {
                            ex.printStackTrace();
                            Log.e(TAG, "Output write SocketException");
                            //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 start
                            if (ex instanceof SocketException) {
                                mRunning = false;
                                mQueue.clear();
                                close(mInput);
                                close(mOutput);
                                close(mSocket);
                                disconnect(mIp);
                            }
                            //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 end
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void sendHeardBeat() {
        Log.d(TAG, "sendHeardBeat: ");
        byte[] bytes = ByteUtil.packHeartBytes();
        putOutMessage(bytes);
    }

    private void checkHeardBreaked() {
        if (SystemClock.elapsedRealtime() - mHeardTime > HEARD_BREAK_TIME) {
            onBeatBreak(mIp);
        }
    }

    private void heardBeat() {
        Log.d(TAG, " heardBeat: ");
        mHeardTime = SystemClock.elapsedRealtime();
    }

    /**
     * Put out message (support for multi-thread)
     *
     * @param msg the msg
     */
    public void putOutMessage(byte[] msg) {
        mQueue.offer(msg);
    }

    /**
     * Finish the socket thread. (after output msg has all sended)
     * send All msg, then finish
     */
    public void finish() {
        if (mHeardBeatRunnable != null) {
            AppExecutor.getInstance().scheduledExecutor().cancel(mHeardBeatRunnable);
        }
        mRunning = false;
        AppExecutor.getInstance().doOnWorkThread(() -> {
            try {
                synchronized (this) {
                    while (!mQueue.isEmpty()) {
                        Log.d(TAG, "finish in work: ");
                        byte[] bytes = mQueue.poll();
                        if (bytes == null) {
                            continue;
                        }
                        Log.d(TAG, "send run finish: " + new String(bytes));
                        byte[] packBytes = ByteUtil.packBytes(bytes);
                        mOutput.write(packBytes);
                        mOutput.flush();
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
                Log.e(TAG, "Output write SocketException");
            } finally {
                // add delay time to avoid msg receive or output failed
                AppExecutor.getInstance().scheduledExecutor().postDelay(() -> {
                    close(mInput);
                    close(mOutput);
                    close(mSocket);
                }, 200);
            }
        });
    }

    public void close(Closeable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (IOException e) {
                Log.d("SafeCloseUtil", "SafeCloseUtil close: " + e.getMessage());
            }
        }
    }

    /**
     * Parse data.
     *
     * @param bytes the bytes
     * @param srcIp the src ip
     */
    public abstract void parseData(byte[] bytes, String srcIp);

    /**
     * Disconnect.
     *
     * @param srcIp the src ip
     */
    public abstract void disconnect(String srcIp);

    /**
     * onBeatBreak
     *
     * @param srcIp srcIp
     */
    protected abstract void onBeatBreak(String srcIp);
}
